
local alien = require 'alien'
local alienex = require 'alienex'
local imageio = require 'imageio'
local jpeg = require 'bindings.libjpeg'
local debug = require 'debug'
local cowrap, yield
    = coroutine.wrap, coroutine.yield
local print
    = print

setfenv(1, imageio)

local BUF_LEN = 4096

imageio.add_handler{
  pattern = '\255\216\255\224..JFIF%z';
  reader = function(f)
    local boxed_ptr = alien.buffer(alien.sizeof('pointer'))
    
    local err = jpeg.jpeg_error_mgr:new()
    local info = jpeg.jpeg_decompress_struct:new()
    info.err = jpeg.jpeg_std_error(err())
    jpeg.jpeg_create_decompress(info())
    
    local src = jpeg.jpeg_source_mgr:new()
    info.src = src()
    local init_source = alien.callback(
      function(info_ptr)
        print("init_source")
        src.next_input_byte = nil
        src.bytes_in_buffer = 0
      end, jpeg.jpeg_source_mgr.init_source)
    
    local fill_input_buffer = alien.callback(
      function(info_ptr)
        print("fill_input_buffer")
        local data = f:read(BUF_LEN)
        src.next_input_byte = data
        src.bytes_in_buffer = #(data or '')
        return 1
      end, jpeg.jpeg_source_mgr.fill_input_buffer)
    
    local skip_input_data = alien.callback(
      function(info_ptr, num_bytes)
        print("skip_input_data")
        f:seek('cur', num_bytes)
        src.next_input_byte = nil
        src.bytes_in_buffer = 0
      end, jpeg.jpeg_source_mgr.skip_input_data)
    
    local resync_to_restart = alien.callback(
      function(info_ptr, desired)
        print("resync_to_restart")
        return 1
      end, jpeg.jpeg_source_mgr.resync_to_restart)
    
    local term_source = alien.callback(
      function(info_ptr)
        print("term_source")
      end, jpeg.jpeg_source_mgr.term_source)
    
    src.init_source = init_source
    src.fill_input_buffer = fill_input_buffer
    src.skip_input_data = skip_input_data
    src.resync_to_restart = resync_to_restart
    src.term_source = term_source
    
    local res = jpeg.jpeg_read_header(info(), 1)
    if (res ~= 1) then
      jpeg.jpeg_destroy_decompress(info())
      error('unable to read header')
    end
    
    jpeg.jpeg_start_decompress(info())
    
    local width, height = info.output_width, info.output_height
    local bpp = info.output_components * 8
    local pitch = bpp * width
    
    local row_count = 8
    
    local row_ptr_array = alien.array('pointer', row_count)
    for i = 1, row_count do
      row_ptr_array[i] = alien.buffer(pitch)
    end
    
    return imageio.create_reader{
      width = width;
      height = height;
      format = 'b8g8r8';
      bpp = bpp;
      pitch = pitch;
      getdata = cowrap(function()
        while (info.output_scanline < info.output_height) do
          local rows_read = jpeg.jpeg_read_scanlines(info(), row_ptr_array.buffer, row_count)
          for i = 1, rows_read do
            yield(info.output_scanline - rows_read + i, 1, row_ptr_array[i])
          end
        end
        jpeg.jpeg_destroy_decompress(info())
      end);
      close = function()
        jpeg.jpeg_destroy_decompress(info())
      end;
      
      -- keep these alive
      init_source = init_source;
      fill_input_buffer = fill_input_buffer;
      skip_input_data = skip_input_data;
      resync_to_restart = resync_to_restart;
      term_source = term_source;
    }
  end;
}
